// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/color_picker.h"

#include "base/bind.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/common/cursors/webcursor.h"
#include "content/public/common/screen_info.h"
#include "third_party/WebKit/public/platform/WebCursorInfo.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/geometry/size_conversions.h"

namespace content {
namespace protocol {

    ColorPicker::ColorPicker(ColorPickedCallback callback)
        : callback_(callback)
        , enabled_(false)
        , last_cursor_x_(-1)
        , last_cursor_y_(-1)
        , host_(nullptr)
        , weak_factory_(this)
    {
        mouse_event_callback_ = base::Bind(
            &ColorPicker::HandleMouseEvent,
            base::Unretained(this));
    }

    ColorPicker::~ColorPicker()
    {
    }

    void ColorPicker::SetRenderWidgetHost(RenderWidgetHostImpl* host)
    {
        if (host_ == host)
            return;

        if (enabled_ && host_)
            host_->RemoveMouseEventCallback(mouse_event_callback_);
        ResetFrame();
        host_ = host;
        if (enabled_ && host)
            host->AddMouseEventCallback(mouse_event_callback_);
    }

    void ColorPicker::SetEnabled(bool enabled)
    {
        if (enabled_ == enabled)
            return;

        enabled_ = enabled;
        if (!host_)
            return;

        if (enabled) {
            host_->AddMouseEventCallback(mouse_event_callback_);
            UpdateFrame();
        } else {
            host_->RemoveMouseEventCallback(mouse_event_callback_);
            ResetFrame();

            WebCursor pointer_cursor;
            WebCursor::CursorInfo cursor_info;
            cursor_info.type = blink::WebCursorInfo::TypePointer;
            pointer_cursor.InitFromCursorInfo(cursor_info);
            host_->SetCursor(pointer_cursor);
        }
    }

    void ColorPicker::OnSwapCompositorFrame()
    {
        if (enabled_)
            UpdateFrame();
    }

    void ColorPicker::UpdateFrame()
    {
        if (!host_)
            return;
        RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(host_->GetView());
        if (!view)
            return;

        gfx::Size size = view->GetViewBounds().size();
        view->CopyFromCompositingSurface(
            gfx::Rect(size), size,
            base::Bind(&ColorPicker::FrameUpdated,
                weak_factory_.GetWeakPtr()),
            kN32_SkColorType);
    }

    void ColorPicker::ResetFrame()
    {
        frame_.reset();
        last_cursor_x_ = -1;
        last_cursor_y_ = -1;
    }

    void ColorPicker::FrameUpdated(const SkBitmap& bitmap,
        ReadbackResponse response)
    {
        if (!enabled_)
            return;

        if (response == READBACK_SUCCESS) {
            frame_ = bitmap;
            UpdateCursor();
        }
    }

    bool ColorPicker::HandleMouseEvent(const blink::WebMouseEvent& event)
    {
        last_cursor_x_ = event.x;
        last_cursor_y_ = event.y;
        if (frame_.drawsNothing())
            return true;

        if (event.button == blink::WebMouseEvent::Button::Left && event.type() == blink::WebInputEvent::MouseDown) {
            if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) {
                return true;
            }

            SkAutoLockPixels lock_image(frame_);
            SkColor sk_color = frame_.getColor(last_cursor_x_, last_cursor_y_);
            callback_.Run(SkColorGetR(sk_color), SkColorGetG(sk_color),
                SkColorGetB(sk_color), SkColorGetA(sk_color));
        }
        UpdateCursor();
        return true;
    }

    void ColorPicker::UpdateCursor()
    {
        if (!host_ || frame_.drawsNothing())
            return;

        if (last_cursor_x_ < 0 || last_cursor_x_ >= frame_.width() || last_cursor_y_ < 0 || last_cursor_y_ >= frame_.height()) {
            return;
        }

        RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
            host_->GetView());
        if (!view)
            return;

            // Due to platform limitations, we are using two different cursors
            // depending on the platform. Mac and Win have large cursors with two circles
            // for original spot and its magnified projection; Linux gets smaller (64 px)
            // magnified projection only with centered hotspot.
            // Mac Retina requires cursor to be > 120px in order to render smoothly.

#if defined(OS_LINUX)
        const float kCursorSize = 63;
        const float kDiameter = 63;
        const float kHotspotOffset = 32;
        const float kHotspotRadius = 0;
        const float kPixelSize = 9;
#else
        const float kCursorSize = 150;
        const float kDiameter = 110;
        const float kHotspotOffset = 25;
        const float kHotspotRadius = 5;
        const float kPixelSize = 10;
#endif

        content::ScreenInfo screen_info;
        host_->GetScreenInfo(&screen_info);
        double device_scale_factor = screen_info.device_scale_factor;

        SkBitmap result;
        result.allocN32Pixels(kCursorSize * device_scale_factor,
            kCursorSize * device_scale_factor);
        result.eraseARGB(0, 0, 0, 0);

        SkCanvas canvas(result);
        canvas.scale(device_scale_factor, device_scale_factor);
        canvas.translate(0.5f, 0.5f);

        SkPaint paint;

        // Paint original spot with cross.
        if (kHotspotRadius > 0) {
            paint.setStrokeWidth(1);
            paint.setAntiAlias(false);
            paint.setColor(SK_ColorDKGRAY);
            paint.setStyle(SkPaint::kStroke_Style);

            canvas.drawLine(kHotspotOffset, kHotspotOffset - 2 * kHotspotRadius,
                kHotspotOffset, kHotspotOffset - kHotspotRadius,
                paint);
            canvas.drawLine(kHotspotOffset, kHotspotOffset + kHotspotRadius,
                kHotspotOffset, kHotspotOffset + 2 * kHotspotRadius,
                paint);
            canvas.drawLine(kHotspotOffset - 2 * kHotspotRadius, kHotspotOffset,
                kHotspotOffset - kHotspotRadius, kHotspotOffset,
                paint);
            canvas.drawLine(kHotspotOffset + kHotspotRadius, kHotspotOffset,
                kHotspotOffset + 2 * kHotspotRadius, kHotspotOffset,
                paint);

            paint.setStrokeWidth(2);
            paint.setAntiAlias(true);
            canvas.drawCircle(kHotspotOffset, kHotspotOffset, kHotspotRadius, paint);
        }

        // Clip circle for magnified projection.
        float padding = (kCursorSize - kDiameter) / 2;
        SkPath clip_path;
        clip_path.addOval(SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter));
        clip_path.close();
        canvas.clipPath(clip_path, SkClipOp::kIntersect, true);

        // Project pixels.
        int pixel_count = kDiameter / kPixelSize;
        SkRect src_rect = SkRect::MakeXYWH(last_cursor_x_ - pixel_count / 2,
            last_cursor_y_ - pixel_count / 2,
            pixel_count, pixel_count);
        SkRect dst_rect = SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter);
        canvas.drawBitmapRect(frame_, src_rect, dst_rect, NULL);

        // Paint grid.
        paint.setStrokeWidth(1);
        paint.setAntiAlias(false);
        paint.setColor(SK_ColorGRAY);
        for (int i = 0; i < pixel_count; ++i) {
            canvas.drawLine(padding + i * kPixelSize, padding,
                padding + i * kPixelSize, kCursorSize - padding, paint);
            canvas.drawLine(padding, padding + i * kPixelSize,
                kCursorSize - padding, padding + i * kPixelSize, paint);
        }

        // Paint central pixel in red.
        SkRect pixel = SkRect::MakeXYWH((kCursorSize - kPixelSize) / 2,
            (kCursorSize - kPixelSize) / 2,
            kPixelSize, kPixelSize);
        paint.setColor(SK_ColorRED);
        paint.setStyle(SkPaint::kStroke_Style);
        canvas.drawRect(pixel, paint);

        // Paint outline.
        paint.setStrokeWidth(2);
        paint.setColor(SK_ColorDKGRAY);
        paint.setAntiAlias(true);
        canvas.drawCircle(kCursorSize / 2, kCursorSize / 2, kDiameter / 2, paint);

        WebCursor cursor;
        WebCursor::CursorInfo cursor_info;
        cursor_info.type = blink::WebCursorInfo::TypeCustom;
        cursor_info.image_scale_factor = device_scale_factor;
        cursor_info.custom_image = result;
        cursor_info.hotspot = gfx::Point(kHotspotOffset * device_scale_factor,
            kHotspotOffset * device_scale_factor);

        cursor.InitFromCursorInfo(cursor_info);
        DCHECK(host_);
        host_->SetCursor(cursor);
    }

} // namespace protocol
} // namespace content
